#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <gcc-plugin.h>
#include <stdbool.h>
#include <tree-pass.h>
#include <gimple.h>
#include <cfgloop.h>
#include "c-family/c-pragma.h"	// pour le register pragma
#include "c-family/c-common.h"	// pour les error/warning
#include "vec.h"// pour les vecteurs

#include <gimple-pretty-print.h>


FILE *out = NULL;// creation de fichier de trace static

// pour les warnings
#define GCC_WARN(gmsgid) \
	do { warning (OPT_Wpragmas, gmsgid); return; } while (0)
#define GCC_WARN2(gmsgid, arg) \
	do { warning (OPT_Wpragmas, gmsgid, arg); return; } while (0)  
#define GCC_ERR(gmsgid) \
	do { error (gmsgid); exit(1); } while (0)   
#define GCC_ERR2(gmsgid, arg) \
	do { error (gmsgid, arg); exit(1); }while (0) 

// types
enum op_type
{
	READ,
	WRITE
};

// variables globales
int plugin_is_GPL_compatible;

unsigned int nb_max_functions = 256;
vec<const char*, va_gc, vl_embed> * vec_required_dyn = NULL;		// ici il y a le main en +, réfléchir ...
vec<const char*, va_gc, vl_embed> * vec_required_static = NULL;
vec<const char*, va_gc, vl_embed> * vec_updated = NULL;


/**
 * INFORMATIONS CONCERNANT LE PLUGIN
 **/
static struct plugin_info plugin_infos =
{
	.version = "1.0",
	.help = "Code Instrumentation Project"
};


/**
 * OUTILS POUR LA GESTION DU VECTEUR DE FONCTIONS
 **/

// affichage du vecteur passé en paramètre
void display_vector(vec<const char*,va_gc,vl_embed> * v)
{
	const char * item;
	unsigned index;
	
	if (v != NULL)
	{
		printf("\ncontenu du vecteur :\n");		
		FOR_EACH_VEC_SAFE_ELT(v, index, item)
		{
			printf("index %d : %s\n",index, item);
		}
	}
	else
		GCC_ERR("Tentative d'itération sur vecteur non alloué\n");
}

// teste si un élément est présent dans un vecteur
bool item_in_vector(const char * name, vec<const char*,va_gc,vl_embed> * v)
{
	const char * item;
	unsigned index;
	
	if (v != NULL)
	{
		FOR_EACH_VEC_SAFE_ELT(v, index, item)
		{
			if(strcmp(name, item)==0)
			{
				return true;
			}
		}
		return false;
	}
	else
	{
		error ("Tentative d'itération sur vecteur non alloué\n");
		return false;
	}
}

//insère une fonction dans un vecteur
void push_function(tree t, vec<const char*,va_gc,vl_embed> * v, int &cpt)
{
	if (v != NULL)
	{
		const char * id = IDENTIFIER_POINTER (t);
		if (cpt < nb_max_functions-1)
		{
			if (!item_in_vector(id, v))
			{
				v->quick_push(id);
				cpt++;
			}
			else
			{
				if (v == vec_required_dyn) // pour dédoubler les warnings
					GCC_WARN2("Function %s appears more than once in #pragma instrument arguments.\n",id);
			}
				
		}
		else
		{
			if (v == vec_required_dyn) // pour dédoubler les warnings
			GCC_ERR2("Le nombre de maximum de fonctions analysables est : %d\n.",nb_max_functions-1);
		}
	}
	else
		GCC_ERR("Tentative d'insertion dans un vecteur non alloué\n");
}

// supprime une fonction d'un vecteur
void remove_function(const char * name ,vec<const char*,va_gc,vl_embed> * v)
{
	const char * item;
	unsigned index;
	
	if (v != NULL)
	{
		FOR_EACH_VEC_SAFE_ELT(v, index, item)
		{
			if(strcmp(name, item)==0)
			{
				v->ordered_remove(index);
			}
		}
	}
	else
	{
		GCC_ERR("Tentative de suppression dans un vecteur non alloué\n");		
	}
}

/**
 * PASSE POUR L'INSTRUMENTATION STATIQUE
 **/

enum op_ctx
{
	OP_READ,
	OP_WRITE
};

bool static_gate()
{
	//printf("STATIC PASS GATE %s\n", fndecl_name(cfun->decl));
	const char * function_name = fndecl_name(cfun->decl);
	
	if (item_in_vector(function_name,vec_required_static)) 
	{		
		return true;
	}
	else
		return false ;
}

// récupère le nombre d'itérations d'une boucle
int get_number_iter(struct loop * loop)
{
	if(loop->any_upper_bound)
	{
		double_int iter = loop->nb_iterations_upper_bound;
		int number = iter.to_shwi();
		if (number == int(INT_MAX-2))
		{
			return 1;
		}
		else
		{
			//printf ("Number of iterations : %d\n", number+1);
			return (number+1);
		}		
	}
	else
		{
			return 1;
		}		
}

// initialise les uid des boucles en insérant la pondération
void process_loop(struct loop* loop, int weight)
{
	gimple_stmt_iterator gsi;
	gimple stmt;
	
	if (loop != NULL)
	{
		int iter = get_number_iter(loop);
		weight *= iter;		
		
		if (iter > 0)
		{			
			basic_block * body = get_loop_body(loop);
			for (gsi = gsi_start_bb(*body); !gsi_end_p(gsi); gsi_next(&gsi))
			{
				stmt = gsi_stmt(gsi);
				gimple_set_uid(stmt, weight);
			} 			
		}

		// appel récursif sur les boucles intérieures
		struct loop* inner;
		for (inner = loop->inner ; inner != NULL ; inner = inner->next)
		{
			//printf("Appel Récursif\n"/*: la boucle %d est incluse dans la boucle %d\n"/*, inner->num, loop->num*/);			 
			process_loop(inner, weight);
		}
	}	
}




// initialise tous les uids à 1
void init_weights()
{  
  basic_block bb;
	gimple_stmt_iterator gsi;
	gimple stmt;

	FOR_ALL_BB(bb)
	{
		for (gsi=gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi))
		{
			stmt = gsi_stmt(gsi);
			gimple_set_uid(stmt, 1);
		}
	}
}


// decode une operande et incrémente les compteurs de loads et de stores
void decode_op(tree op, int num_op, int &cpt_loads, int &cpt_stores, int weight)
{	
	switch(TREE_CODE(op))
	{		
		case MEM_REF:// printf("CASE MEM_REF WEIGHT : %d \n", weight);		
		case ARRAY_REF:

			if (num_op == 0)
			{
				cpt_stores += weight;
			}
			else
			{
				cpt_loads += weight;
			}
			break;
	}
}
	
		

unsigned int static_exec()
{
	
	const char * function_name = fndecl_name(cfun->decl);

	basic_block bb;	  
	gimple_stmt_iterator gsi;
	gimple stmt;
		
	// initialisations
	int cpt_loads = 0;
	int cpt_stores = 0;
	init_weights();	
		
	// ecriture du poids des boucles dans les uids
	if(current_loops != NULL)
	{
		process_loop(current_loops->tree_root,1);
	}
	
	// compter les loads / stores
	FOR_ALL_BB(bb)
	{
		for (gsi=gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi))
			{
				stmt = gsi_stmt(gsi);
				int weight = gimple_uid( stmt );
							
				// on ne traite de que les assign
				if (gimple_code( stmt ) == GIMPLE_ASSIGN)
				{
					 int nb_operands = gimple_num_ops(stmt);					
					// aller voir les operandes des statements pour recuperer les accès aux tableaux
					// incrementation des compteurs dans decode_op					
					for (int i=0; i<nb_operands; ++i)	
						decode_op(gimple_op(stmt,i), i, cpt_loads, cpt_stores, weight);
				}
			}
	}
	
	// affichages dans le terminal
	printf("=== Function %s stats =============================\n", function_name);
	printf("cpt_loads %d\n", cpt_loads);
	printf("cpt_stores %d\n", cpt_stores);
	
	// écriture dans le fichier de traces statiques
  fprintf(out,"function %s \n",fndecl_name(cfun->decl));
	fprintf(out,"%d loads\n%d stores\n",cpt_loads, cpt_stores);
	
	return 0;

}

static struct opt_pass static_instrumentation_pass = 
{
	GIMPLE_PASS,
	"Static Instrumentation", 
	0,
	static_gate,
	static_exec
};


/**
 * PASSE POUR L'INSTRUMENTATION DYNAMIQUE 
 **/


bool dynamic_gate()
{
	//printf("DYNAMIC PASS GATE %s\n", fndecl_name(cfun->decl));
	const char * function_name = fndecl_name(cfun->decl);
	if (item_in_vector(function_name,vec_required_dyn)) 
	{
		remove_function(function_name,vec_updated);
		return true;
	}
	else
		return false;
}

// insère des appels de fonctions présentes dans le librairie dynamique 
// à l'entrée et à la sortie de chaque fonction
unsigned int dynamic_exec()
{	
	edge target;
	edge_iterator edge_it;	
	char msg[50]; *msg = '\0';
	char msg2[4]; *msg2 = '\0';		
	tree param_name;
	tree param_req;
	tree func_signature;
	tree func;	
	gimple gs;
	gimple_stmt_iterator gsi;
	
	// signature de la fonction
	func_signature = build_function_type_list(void_type_node, ptr_type_node, NULL_TREE);		
	
	// paramètre nom de la fonction
	snprintf(msg, 50, fndecl_name(cfun->decl));
	param_name = build_string_literal (50, msg);	
	
	// paramètre required
	if (item_in_vector(fndecl_name(cfun->decl), vec_required_dyn))
		snprintf(msg2,4,"oui");		
	else
		snprintf(msg2,4,"non");
	param_req = build_string_literal (4, msg2);		
		
						
	// ajout de l'appel à enter_function, au début de la fonction				
	func = build_fn_decl("enter_function", func_signature);		
	gs=gimple_build_call (func,2,param_name,param_req);
				
	FOR_EACH_EDGE(target,edge_it,ENTRY_BLOCK_PTR->succs)
	{
		gsi = gsi_start_bb(target->dest);
		gsi_insert_before (&gsi, gs, GSI_NEW_STMT);
	}
	
	// ajout de l'appel à exit_function, à la fin de la fonction		
	func = build_fn_decl("exit_function", func_signature);		
	gs=gimple_build_call (func,1,param_name);

	FOR_EACH_EDGE(target,edge_it,EXIT_BLOCK_PTR->preds)
	{			
		gsi = gsi_last_bb(target->src);
		gsi_insert_before (&gsi, gs, GSI_NEW_STMT);		
	}

	return 0;
}


static struct opt_pass dynamic_instrumentation_pass = 
{
	GIMPLE_PASS,
	"Dynamic Instrumentation", 
	0,
	dynamic_gate,
	dynamic_exec
};

/**
 * DIRECTIVE : GESTION DU PRAGMA ET PARSAGE DES ARGUMENTS 
 **/

// parse les arguments passés au pragma
void handle_pragma(struct cpp_reader *ARG_UNUSED(dummy))
{		
	enum cpp_ttype token;
	tree x;
	int cpt = 0;
		
	// ne pas accepter les pragma dans une fonction
	if (cfun)
		GCC_ERR("#pragma GCC option is not allowed inside functions");

	//automate implémenté à l'aide d'une machine à états		
	int etat = 0;
	token = pragma_lex (&x);
 
	while (etat >= 0 && etat != 4)
	{
		switch (etat) 
		{
		case 0:
			if(token == CPP_OPEN_PAREN)
				etat = 1;
			else if (token == CPP_NAME)
			{
				push_function(x,vec_required_dyn,cpt);
				push_function(x,vec_required_static,cpt);
				push_function(x,vec_updated,cpt);
				etat = 3; 
			}
			else if (token == CPP_STRING)
				etat = -1;
			else
				etat = -2;
			if (etat > 0)
				token = pragma_lex (&x);
			break;
		case 1:
			if (token == CPP_NAME)
			{
				push_function(x,vec_required_dyn,cpt);
				push_function(x,vec_required_static,cpt);
				push_function(x,vec_updated,cpt);
				etat = 2;
			}
			else if (token == CPP_STRING)
				etat = -1;
			else if (token == CPP_COMMA)
				etat = -4;
			else if (token == CPP_CLOSE_PAREN)
				etat = -6;	
			else
				etat = -2;
			if (etat > 0)
				token = pragma_lex (&x);
			break;
		case 2:
			if (token == CPP_CLOSE_PAREN)
				etat = 3;
			else if (token == CPP_COMMA)
				etat = 1;
			else if (token == CPP_NAME)
				etat = -3;
			else if (token == CPP_EOF)
				etat = -5;
			else
				etat = -2; 
			if (etat > 0)
				token = pragma_lex (&x);
			break;
		case 3:				
			if (token != CPP_EOF)
			{
				if (cpt == 1 && (token==CPP_NAME || token == CPP_COMMA))
					GCC_ERR("#pragma badly formed : If you want to specify more than one function, please use parenthesis : (f0,f1, ... ,fn) ");
				else
					GCC_ERR("#pragma badly formed : junk at end of #pragma message");
			}
			else if (token == CPP_EOF)
				etat = 4;					
			break;				
		default:
			etat = -8;
			break;
		}
	}
	
	// instrumentation du main
	if (!item_in_vector("main", vec_required_dyn))
	{
		vec_required_dyn->quick_push("main");
		cpt ++;
	}
	
	// gestion des messages d'erreurs
	if (etat == -1)
		GCC_ERR("#pragma badly formed : don't put the function names between double quotes.");
	else if (etat == -2)
		GCC_ERR("#pragma badly formed : garbage read.");
	else if (etat == -3)
		GCC_ERR("#pragma badly formed : missing comma.");
	else if (etat == -4)
		GCC_ERR("#pragma badly formed : consecutive commas.");
	else if (etat == -5)
		GCC_ERR("#pragma badly formed : missing close parenthesis.");
	else if (etat == -6)
		GCC_ERR("#pragma badly formed : expected function name, read closing parenthesis.");	
		
}


//pragma declaration
void register_pragma (void *event_data, void *data)
{
	c_register_pragma("instrument", "function", handle_pragma);
}


/**
 * GESTION DU PLUGIN : INIT ET RELEASE 
 **/

// vérifie que les passes instrumentées ont bien été traitées
// libère les vecteurs alloués de manière dynamique
// ferme les fichiers ouverts en écriture
void plugin_release(void *gcc_data, void *user_data)
{
	const char * item;
	unsigned index;
	
	if (vec_updated != NULL)
	{
		FOR_EACH_VEC_SAFE_ELT(vec_updated, index, item)
		{
			warning(OPT_Wpragmas, "Function \"%s\" instrumented, but does not exist in code.\n", item);
		}
	}
	else
		GCC_ERR("Tentative d'itération sur vecteur non alloué\n");
	
	// libération des vecteurs alloués dynamiquement
	vec_free(vec_updated);
	vec_free(vec_required_dyn);	
	vec_free(vec_required_static);
	
	// fermeture du fichier static trace
	if( !out )
		GCC_ERR("Tentative de fermeture d'un fichier inexistant\n");
	fclose( out );
	out = NULL;

}

int plugin_init (struct plugin_name_args *plugin_ctx, struct plugin_gcc_version *version)
{	

	// Check GCC version
	if( strncmp( version->basever, "4.8", strlen("4.8") ) ) 
	{
		printf("Error bad GCC version (%s) instead of 4.8.*\n", version->basever);
		return -1;
	}
	
	// dynamic allocations
	vec_alloc(vec_required_dyn,nb_max_functions);
	vec_alloc(vec_required_static,nb_max_functions);	
	vec_alloc(vec_updated,nb_max_functions);
	
	// DYNAMIC pass informations 
	struct register_pass_info dynamic_pass;
	dynamic_pass.pass = &dynamic_instrumentation_pass;
	dynamic_pass.reference_pass_name = "cfg";
	dynamic_pass.ref_pass_instance_number = 1;
	dynamic_pass.pos_op = PASS_POS_INSERT_AFTER;

	// file opening for static traces
	out = fopen("trace/static_trace", "w");

	// STATIC pass informations 
	struct register_pass_info static_pass;
	static_pass.pass = &static_instrumentation_pass;
	static_pass.reference_pass_name = "*record_bounds";
	static_pass.ref_pass_instance_number = 1;
	static_pass.pos_op = PASS_POS_INSERT_AFTER;
		
	// Register the pass
	register_callback(plugin_ctx->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &dynamic_pass);
	register_callback(plugin_ctx->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &static_pass);		
	
	// Register plugin infos 
	register_callback(plugin_ctx->base_name, PLUGIN_INFO, NULL, &plugin_infos );

	// Register a callback for cleanup 
	register_callback(plugin_ctx->base_name, PLUGIN_FINISH, plugin_release, NULL );
	
	// Register a callback for pragmas 
	register_callback(plugin_ctx->base_name, PLUGIN_PRAGMAS, register_pragma, NULL);	



	return 0;
}
